本宏用于:将一份“源数据表”按规则筛选、清洗后,生成结构化的“输出明细表”。

主要动作:

  1. 自动识别源表关键列(数值列 / 文本列 / 标记列 / 复制截止列);

  2. 逐行判定:保留有效行、静默过滤空行、对命中标记的行在源表标红;

  3. 整块读入内存再批量写回,提升性能;

  4. 后处理:加自动筛选、冻结窗格、缩放、统一数字格式与对齐;

  5. 将本次复制行数写入明细表指定单元格,并弹出运行报告。

' ============================================================================
' 1. 【核心结构体定义】
' ============================================================================
Public Type ColumnInfo
    DecimalStartCol As Long
    DecimalEndCol As Long
    SpecialTextCol As Long
    lastColToCopy As Long
    InvalidCol As Long      ' 标记判定列(AQ)索引
End Type

' ============================================================================
' 2. 【精简高效配置区域】  ← 所有“用户可能修改的参数”集中在此,逻辑代码无需改动
'     使用约定:只改本区域的值即可;所有项均为编译期常量,WPS 导入即生效。
' ============================================================================

' ---- 工作表与行列位置 ----
Public Const SOURCE_SHEET_NAME As String = "源数据表"   ' 数据源工作表名(读取方);若监控表改名需同步修改此处
Public Const TARGET_SHEET_NAME As String = "输出明细表"   ' 输出工作表名(每次运行先删后建,确保结果干净)
Public Const DATA_START_ROW As Long = 7        ' 数据起始行:表头占 1~6 行,第 7 行起为正式数据
Public Const HEADER_START_ROW As Long = 1      ' 表头起始行
Public Const HEADER_END_ROW As Long = 6        ' 表头结束行;同时也是 FREEZE_ROWS 的默认值来源(见下方)

' ---- 列识别关键词(按表头文字“全词精准匹配”,匹配前会先去掉空格/换行/非断空格并转大写)----
Public Const EXCLUDE_COL_HEADER As String = "截止列标识"   ' 命中该表头后,该列及其右侧所有列都不复制到明细表(作为复制右边界)
' 注意:DECIMAL_COL_HEADERS 按历史约定(#3)保持原样未改;多个列名用半角逗号分隔
Public Const DECIMAL_COL_HEADERS As String = "数值字段1,数值字段2,数值字段3,数值字段4,数值字段5,数值字段6,数值字段7,数值字段8,数值字段9,数值字段10,数值字段11,数值字段12,数值字段13,数值字段14,数值字段15,数值字段16,数值字段17,数值字段18,数值字段19"
Public Const SPECIAL_TEXT_HEADER As String = "文本标识"      ' 文本列:整列强制文本格式(@),防止编码/前导零被 Excel 自动转换;多列用逗号分隔
Public Const INVALID_COL_HEADER As String = "标记判定"   ' “哪一列”负责标红判定(当前=AQ 列);VBA 只读取它,判定逻辑在工作表里
Public Const INVALID_MARK_TEXT As String = "剔除"        ' “什么值”算无效:AQ 列文字命中本值即标红(与“哪一列”是两个独立维度,勿混淆)

' ---- 末行探测列 ----
Public Const INDEX_COL_LETTER As String = "AR"   ' 用该列(同标识索引列)定位真实末行;避免 A 列中部空行导致末行被提前截断
                                                ' 若该列整列为空,则退化为全表 Find(见 ProcessProductionData 第 89~92 行)

' ---- 外观与格式 ----
Public Const SHEET_ZOOM_PERCENT As Long = 90    ' 明细表显示缩放比例(%)
Public Const SHEET_TAB_COLOR As Long = 255      ' 明细表标签颜色(VBA 色值,255=红色)
Public Const MARK_COLOR As Long = &HCCCCFF      ' 标红底色 = RGB(255,204,204) 浅红(&HCCCCFF 即数值 13421823,用十六进制便于阅读)
Public Const DECIMAL_NUMBER_FORMAT As String = "#,##0.00_);(#,##0.00)"   ' 小数列数字格式:千分位+两位小数,负数用括号显示
Public Const TEXT_NUMBER_FORMAT As String = "@"                          ' 文本列数字格式:强制按文本存储

' ---- 行为开关(改 True/False 即可切换)----
Public Const BLANK_ZERO_DECIMALS As Boolean = True   ' 小数列“真零”是否显示为空:True=隐藏零值(更干净),False=保留 0
Public Const ENABLE_PERFORMANCE_MODE As Boolean = True   ' 是否开启加速四件套(ScreenUpdating/Calculation/EnableEvents/DisplayAlerts);调试想看中间过程设 False
Public Const ENABLE_HEADER_FILTER As Boolean = True      ' 是否给表头行加自动筛选下拉(明细表常用,不需要可关)

' ---- 冻结窗格 ----
Public Const ENABLE_FREEZE_PANES As Boolean = True   ' 是否冻结窗格
Public Const FREEZE_ROWS As Long = HEADER_END_ROW    ' 冻结“前几行”(默认=表头 6 行;与表头行解耦,可单独设如 4)
Public Const FREEZE_COLS As Long = 10                 ' 冻结“前几列”(默认 0=不冻结列;设 2 可固定左侧两列标识列)。注意须 ≥ 0(内部已做防御)

' ============================================================================
' 3. 【主处理程序 ProcessProductionData】   入口:在 WPS 中运行本宏即可
'     整体流程:
'       定位源表 → 开启性能模式 → 删除旧明细表并新建 → 探测真实末行并清洗底色 →
'       识别关键列(AutoDetectColumnsScheme2) → 复制表头 → 内存批处理数据行(ProcessDataRowsArray) →
'       后处理美化(筛选/冻结/缩放/格式, ApplyFormatting) → 恢复环境 → 弹出运行报告。
'     安全:全程挂全局错误守护(ErrHandler),任何中途报错都能恢复 Application 环境,避免 WPS 卡死。
' ============================================================================
Public Sub ProcessProductionData()
    Dim startTime As Double: startTime = Timer
    Dim ws As Worksheet, destSheet As Worksheet
    Dim colInfo As ColumnInfo
    Dim lastRow As Long, destRow As Long, copyCount As Long, redCount As Long, totalRows As Long

    On Error Resume Next
    Set ws = ThisWorkbook.Sheets(SOURCE_SHEET_NAME)
    On Error GoTo 0
    If ws Is Nothing Then MsgBox "未找到源工作表!", vbExclamation: Exit Sub

    ' 开启低性能环境极致加速防线(受 ENABLE_PERFORMANCE_MODE 控制)
    If ENABLE_PERFORMANCE_MODE Then
        With Application
            .ScreenUpdating = False
            .Calculation = xlCalculationManual
            .EnableEvents = False
            .DisplayAlerts = False
        End With
    End If

    On Error GoTo ErrHandler   ' #4 全局错误守护:中途报错也能恢复环境,避免 Excel 卡死

    ' 冲突删除原有的明细表
    On Error Resume Next
    ThisWorkbook.Sheets(TARGET_SHEET_NAME).Delete
    On Error GoTo 0
    On Error GoTo ErrHandler   ' #4 重新挂回全局守护

    ' 新建目标表
    Set destSheet = ThisWorkbook.Sheets.Add(Before:=ws)
    destSheet.Name = TARGET_SHEET_NAME
    destSheet.Tab.Color = SHEET_TAB_COLOR

    ' 获取边界并清洗底色(#5 固定到 INDEX_COL_LETTER[同文本标识索引]列取真实末行,避免 A 列中部空行截断)
    Dim lastCell As Range
    Set lastCell = ws.Columns(INDEX_COL_LETTER).Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious)
    If lastCell Is Nothing Then
        Set lastCell = ws.Cells.Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious)
    End If
    If lastCell Is Nothing Then lastRow = 0 Else lastRow = lastCell.Row

    If lastRow >= DATA_START_ROW Then
        ws.Range(ws.Rows(DATA_START_ROW), ws.Rows(lastRow)).Interior.ColorIndex = xlNone
        totalRows = lastRow - DATA_START_ROW + 1
    Else
        totalRows = 0
    End If

    colInfo = AutoDetectColumnsScheme2(ws)

    ' 复制表头(#6 用值+数字格式,避免把监控表公式带进明细表;再补格式保留表头外观)
    If colInfo.lastColToCopy > 0 Then
        ws.Range(ws.Cells(HEADER_START_ROW, 1), ws.Cells(HEADER_END_ROW, colInfo.lastColToCopy)).Copy
        destSheet.Range(destSheet.Cells(HEADER_START_ROW, 1), destSheet.Cells(HEADER_END_ROW, colInfo.lastColToCopy)).PasteSpecial xlPasteValuesAndNumberFormats
        destSheet.Range(destSheet.Cells(HEADER_START_ROW, 1), destSheet.Cells(HEADER_END_ROW, colInfo.lastColToCopy)).PasteSpecial xlPasteFormats
        Application.CutCopyMode = False
    End If

    destRow = DATA_START_ROW
    ProcessDataRowsArray ws, destSheet, destRow, copyCount, redCount, colInfo, lastRow

    ' 将本次成功复制的行数写入明细表 A5 单元格(放在表头复制之后,避免被 PasteSpecial 覆盖)
    destSheet.Cells(5, 1).Value = copyCount

    ' 后处理美化:无条件调用(空表仅保留筛选/冻结/缩放;有数据再铺格式,详见 ApplyFormatting 内部空表保护)
    ApplyFormatting ws, destSheet, destRow, colInfo

    ' 环境恢复(受 ENABLE_PERFORMANCE_MODE 控制)
    If ENABLE_PERFORMANCE_MODE Then
        With Application
            .ScreenUpdating = True
            .Calculation = xlCalculationAutomatic
            .EnableEvents = True
            .DisplayAlerts = True
        End With
    End If

    If Not destSheet Is Nothing Then destSheet.Activate

    MsgBox "处理完成!" & vbCrLf & _
           "----------------------------------" & vbCrLf & _
           "本次运行耗时:" & Format(Timer - startTime, "0.00") & " 秒" & vbCrLf & _
           "源表数据总计:" & totalRows & " 行" & vbCrLf & _
           "----------------------------------" & vbCrLf & _
           "成功复制行数:" & copyCount & " 行" & vbCrLf & _
           "过滤标红行数:" & redCount & " 行", vbInformation, "运行报告 by:蛋蛋之家"
    Exit Sub

ErrHandler:
    If ENABLE_PERFORMANCE_MODE Then
        With Application
            .ScreenUpdating = True
            .Calculation = xlCalculationAutomatic
            .EnableEvents = True
            .DisplayAlerts = True
        End With
    End If
    Application.CutCopyMode = False
    MsgBox "运行出错(已自动恢复环境):" & Err.Description, vbCritical, "错误 by:***"
End Sub

' ============================================================================
' 4. 【表头比对函数:扫描表头行(第6行),识别各类关键列的位置】
'     算法:遍历表头每个单元格 → 清洗(去空格/换行/非断空格并转大写) → 与各关键词数组做“全词匹配”。
'     产出 ColumnInfo:小数列起止(DecimalStart/EndCol)、文本列(SpecialTextCol)、
'           复制截止列(lastColToCopy)、标记判定列(InvalidCol)。
'     语义说明:EXCLUDE 决定“复制右边界”;DECIMAL 决定数字格式区间;
'           SPECIAL 决定文本格式列;INVALID 决定标红判定列(仅读取,不参与格式)。
'           多个关键词用逗号分隔,匹配时同样会做清洗,故表头带空格也能命中。
' ============================================================================
Public Function AutoDetectColumnsScheme2(ws As Worksheet) As ColumnInfo
    Dim info As ColumnInfo
    Dim lastCol As Long, i As Long, j As Long
    Dim cleanText As String
    Dim excludeArr() As String: excludeArr = Split(EXCLUDE_COL_HEADER, ",")
    Dim decimalArr() As String: decimalArr = Split(DECIMAL_COL_HEADERS, ",")
    Dim specialArr() As String: specialArr = Split(SPECIAL_TEXT_HEADER, ",")
    Dim invalidArr() As String: invalidArr = Split(INVALID_COL_HEADER, ",")

    Dim excludeStartCol As Long
    lastCol = ws.Cells(HEADER_END_ROW, ws.Columns.Count).End(xlToLeft).Column
    excludeStartCol = lastCol + 1
    info.DecimalStartCol = lastCol + 1
    info.DecimalEndCol = 0
    info.InvalidCol = 0

    For i = 1 To lastCol
        cleanText = ws.Cells(HEADER_END_ROW, i).Value
        cleanText = Replace(Replace(Replace(Replace(cleanText, " ", ""), vbCr, ""), vbLf, ""), Chr(160), "")
        cleanText = UCase(Trim(cleanText))

        If cleanText <> "" Then
            For j = LBound(excludeArr) To UBound(excludeArr)
                If cleanText = UCase(Trim(Replace(excludeArr(j), " ", ""))) Then
                    excludeStartCol = IIf(i < excludeStartCol, i, excludeStartCol)
                End If
            Next j
            For j = LBound(specialArr) To UBound(specialArr)
                If cleanText = UCase(Trim(Replace(specialArr(j), " ", ""))) Then info.SpecialTextCol = i
            Next j
            For j = LBound(invalidArr) To UBound(invalidArr)
                If cleanText = UCase(Trim(Replace(invalidArr(j), " ", ""))) Then info.InvalidCol = i
            Next j
            For j = LBound(decimalArr) To UBound(decimalArr)
                If cleanText = UCase(Trim(Replace(decimalArr(j), " ", ""))) Then
                    info.DecimalStartCol = IIf(i < info.DecimalStartCol, i, info.DecimalStartCol)
                    info.DecimalEndCol = IIf(i > info.DecimalEndCol, i, info.DecimalEndCol)
                End If
            Next j
        End If
    Next i

    info.lastColToCopy = IIf(excludeStartCol - 1 < 1, lastCol, excludeStartCol - 1)

    If info.DecimalStartCol > info.lastColToCopy Then info.DecimalStartCol = 1
    If info.DecimalEndCol = 0 Or info.DecimalEndCol > info.lastColToCopy Then info.DecimalEndCol = info.lastColToCopy

    AutoDetectColumnsScheme2 = info
End Function

' ============================================================================
' 5. 【内存数据交互:源表读入数组 → 逐行判定 → 批量写回目标表】
'     设计:整块读入内存数组再写回,避免逐单元格读写;配合性能模式可大幅提速。
'     判定三态 act: 1=复制  0=静默过滤(标记判定列 AQ 为空)  -1=标红过滤(命中 INVALID_MARK_TEXT)
'     注意:标红行只在“源表”上色(由主流程收集 redRange 批量上色),目标表不含标红行。
' ============================================================================
Public Sub ProcessDataRowsArray(srcWs As Worksheet, destWs As Worksheet, ByRef destRow As Long, ByRef copyCount As Long, ByRef redCount As Long, colInfo As ColumnInfo, lastRow As Long)
    If lastRow < DATA_START_ROW Or colInfo.lastColToCopy < 1 Then Exit Sub

    Dim srcData As Variant
    Dim readCol As Long
    readCol = colInfo.lastColToCopy
    If colInfo.InvalidCol > readCol Then readCol = colInfo.InvalidCol   ' 读取范围至少覆盖 AQ 列,便于判定
    srcData = srcWs.Range(srcWs.Cells(1, 1), srcWs.Cells(lastRow, readCol)).Value

    If Not IsArray(srcData) Then
        Dim tempArr As Variant: ReDim tempArr(1 To lastRow, 1 To readCol)
        tempArr(lastRow, readCol) = srcData: srcData = tempArr
    End If

    Dim destData As Variant: ReDim destData(1 To UBound(srcData, 1), 1 To colInfo.lastColToCopy)
    Dim i As Long, j As Long
    Dim redRange As Range

    For i = DATA_START_ROW To lastRow
        Dim act As Long   ' 1=复制  0=静默过滤(空行/无索引)  -1=标红过滤(AQ=无效)
        act = DecideRow(srcData, i, colInfo)
        If act = 1 Then
            copyCount = copyCount + 1
            For j = 1 To colInfo.lastColToCopy
                If j = colInfo.SpecialTextCol Then
                    ' #2 用数组值的 CStr,去掉慢且受显示格式影响的 .Text
                    Dim specVal As Variant: specVal = srcData(i, j)
                    If IsError(specVal) Then
                        destData(copyCount, j) = ""
                    Else
                        destData(copyCount, j) = CStr(specVal)
                    End If
                Else
                    Dim tempVal As Variant: tempVal = srcData(i, j)
                    If IsError(tempVal) Then
                        destData(copyCount, j) = ""
                    ElseIf j >= colInfo.DecimalStartCol And j <= colInfo.DecimalEndCol And IsNumeric(tempVal) And Val(tempVal) = 0 Then
                        If BLANK_ZERO_DECIMALS Then destData(copyCount, j) = "" Else destData(copyCount, j) = tempVal
                    Else
                        destData(copyCount, j) = tempVal
                    End If
                End If
            Next j
        ElseIf act = -1 Then
            ' 标红:AQ=无效,收集到 redRange 循环后批量上色(避免逐行写表,提速)
            If redRange Is Nothing Then Set redRange = srcWs.Rows(i) Else Set redRange = Union(redRange, srcWs.Rows(i))
            redCount = redCount + 1
        Else
            ' act = 0:标记判定列(AQ)为空 → 静默过滤,不复制也不标红
        End If
    Next i

    ' 批量标红(非核心操作,失败也不影响复制结果)
    If Not redRange Is Nothing Then
        On Error Resume Next
        redRange.Interior.Color = MARK_COLOR
        On Error GoTo 0
    End If

    If copyCount > 0 Then
        If colInfo.SpecialTextCol > 0 And colInfo.SpecialTextCol <= colInfo.lastColToCopy Then destWs.Columns(colInfo.SpecialTextCol).NumberFormat = TEXT_NUMBER_FORMAT
        destWs.Cells(DATA_START_ROW, 1).Resize(copyCount, colInfo.lastColToCopy).Value = destData
        destRow = DATA_START_ROW + copyCount
    End If
End Sub

' ============================================================================
' 6. 【行判定:真正的判定由工作表 AQ 列(标记判定) 完成,VBA 只负责读取结果】
'     返回三态: 1=复制  0=静默过滤(标记判定列为空)  -1=标红过滤(命中 INVALID_MARK_TEXT)
'     设计要点:除“明确命中无效文字”外,其余情况(含公式错误/读取异常/空值)一律“保守复制”,
'               绝不让任何数据被静默删除;空值仅走静默过滤(不复制也不标红)。
' ============================================================================
Public Function DecideRow(srcData As Variant, rowNum As Long, colInfo As ColumnInfo) As Long
    Dim v As Variant
    If colInfo.InvalidCol > 0 Then
        ' 仅把“数组读取”这一句包在错误屏蔽里,避免把后续逻辑的错误也吞掉(曾整段屏蔽,有数据丢失风险)
        On Error Resume Next
        v = srcData(rowNum, colInfo.InvalidCol)
        If Err.Number <> 0 Then On Error GoTo 0: DecideRow = 1: Exit Function   ' 读取异常→保守复制,防数据静默丢失
        On Error GoTo 0
        If IsError(v) Then
            DecideRow = 1            ' 该单元格是公式错误(#N/A 等):保守复制,避免误杀
        ElseIf UCase(Trim(CStr(v))) = UCase(Trim(INVALID_MARK_TEXT)) Then
            DecideRow = -1           ' 命中“无效”文字 → 标红(由主流程批量上色)
        ElseIf Trim(CStr(v)) = "" Then
            DecideRow = 0            ' 标记判定列(AQ)为空 → 静默过滤,不复制也不标红
        Else
            DecideRow = 1            ' 其它内容 → 有效,复制
        End If
    Else
        ' 标记判定列缺失:无法判定,保守复制所有行(避免误删数据)
        DecideRow = 1
    End If
End Function

' ============================================================================
' 7. 【后处理格式美化】  #8 用显式窗口对象替代脆弱的 ActiveWindow
' ============================================================================
Public Sub ApplyFormatting(srcWs As Worksheet, destWs As Worksheet, destRow As Long, colInfo As ColumnInfo)
' 后处理:给明细表加筛选/冻结/缩放,并把表头外观铺到数据区、统一对齐与数字格式。
' 本过程“无条件”被主程序调用(即使一行数据都没复制),因此内部对依赖数据区的步骤做了空表保护。

    ' ① 表头自动筛选(受 ENABLE_HEADER_FILTER 控制;明细表每次重建,不会叠加旧筛选)
    If ENABLE_HEADER_FILTER Then destWs.Rows(HEADER_END_ROW).AutoFilter
    destWs.Activate   ' 冻结依赖 ActiveWindow,必须先激活目标表

    ' ② 窗口缩放 + 冻结(这两步不依赖是否有数据行,空表也生效)
    Dim wnd As Window
    Set wnd = ActiveWindow
    If Not wnd Is Nothing Then
        wnd.Zoom = SHEET_ZOOM_PERCENT
        If ENABLE_FREEZE_PANES Then
            ' FreezePanes 冻结的是“当前选中单元格的左上角”,所以:
            '   选中 (FREEZE_ROWS+1 行, FREEZE_COLS+1 列) → 冻结其“前 R 行 + 前 C 列”。
            ' 用 Max(1,...) 兜底,防止 FREEZE_ROWS/FREEZE_COLS 被误设为负导致 Cells 索引非法而报错。
            Dim fr As Long: fr = Application.WorksheetFunction.Max(1, FREEZE_ROWS + 1)
            Dim fc As Long: fc = Application.WorksheetFunction.Max(1, FREEZE_COLS + 1)
            destWs.Cells(fr, fc).Select
            wnd.FreezePanes = True
        End If
    End If

    ' ③ 以下操作都依赖“存在数据行”(目标区从 DATA_START_ROW 到 destRow-1)。
    '    若一行都没复制(destRow 仍 = DATA_START_ROW),则 destRow-1 < DATA_START_ROW,
    '    数据区为空,下面三小步安全跳过,仅保留上面的筛选/冻结/缩放。
    If destRow - 1 >= DATA_START_ROW Then
        ' ③a) 把源表表头行(第6行)的格式铺到明细表数据区,使数据行拥有与表头一致的外观(底色/边框等)
        srcWs.Range(srcWs.Cells(HEADER_END_ROW, 1), srcWs.Cells(HEADER_END_ROW, colInfo.lastColToCopy)).Copy
        destWs.Range(destWs.Cells(DATA_START_ROW, 1), destWs.Cells(destRow - 1, colInfo.lastColToCopy)).PasteSpecial xlPasteFormats
        Application.CutCopyMode = False

        ' ③b) 数据区统一对齐/换行/取消加粗(覆盖 ③a 带入的表头对齐偏好,确保数据区观感统一)
        '      注意:不加粗是显式设定,仅作用于数据区,表头本身的加粗不受影响。
        With destWs.Range(destWs.Cells(DATA_START_ROW, 1), destWs.Cells(destRow - 1, colInfo.lastColToCopy))
            .WrapText = False
            .Rows.AutoFit
            .HorizontalAlignment = xlCenter
            .VerticalAlignment = xlCenter
            .Font.Bold = False
        End With

        ' ③c) 小数列套用数字格式(文本列已在 ProcessDataRowsArray 中单独设为 @,此处不会与之冲突)
        If colInfo.DecimalStartCol <= colInfo.DecimalEndCol And colInfo.DecimalStartCol > 0 Then
            destWs.Range(destWs.Cells(DATA_START_ROW, colInfo.DecimalStartCol), _
                         destWs.Cells(destRow - 1, colInfo.DecimalEndCol)).NumberFormat = DECIMAL_NUMBER_FORMAT
        End If
    End If
End Sub